﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;

namespace Solution.Test.utils
{
    class DataGenerator
    {
        private string[] logs;
        private bool normal = false;             //set this to true to make data generation less crazy
        private bool allowNegSpeed = true;       //set this to false to prevent data from containing negative speeds
        private int size = 1000;                 //number of data lines to write
        static Random r = new Random();

        /*
         * generates next accelerometer data value based on the previous one
         */
        private double newAcc(double previous)
        {
            double diff;
            if (this.normal)
                diff = r.NextDouble();
            else
                switch (r.Next(10))
                {
                    case 0:
                        diff = r.Next(3) + r.NextDouble();
                        break;
                    default:
                        diff = r.NextDouble();
                        break;
                }

            if (r.Next(2) == 0) return previous + diff;
            else return previous - diff;
        }

        /*
         * generates realistic next speed value based on the previous one
         */
        private double newSpeed(double previous)
        {
            double ret;
            double diff = r.Next(5) + r.NextDouble();
            if (r.Next(2) == 0) ret = previous + diff;
            else ret = previous - diff;

            if (!this.allowNegSpeed && ret < 0)
                ret = newSpeed(previous);
            return ret;
        }

        public DataGenerator(bool extreme, bool negSpeed, int size=1000) : this()
        {
            if (!extreme)
                this.normal = true;

            if (!negSpeed)
                this.allowNegSpeed = false;

            this.size = size;
        }

        public DataGenerator()
        {
            List<Snapshot> entries = new List<Snapshot>();
            DateTime time = DateTime.Now;
            Snapshot prev;

            Snapshot z = new Snapshot();
            z.Speed = 0;
            z.XAcceleration = 0;
            z.YAcceleration = 0;
            z.ZAcceleration = -9.8;

            //first 3 entries are calibration entries
            for (int i = 0; i < 3; i++)
            {
                z.Recorded = time;
                time = time.AddSeconds(1);
                entries.Add(z);
            }

            //base values
            Snapshot sz = new Snapshot();
            sz.Speed = 60;
            sz.XAcceleration = 1;
            sz.YAcceleration = 0;
            sz.ZAcceleration = -9.8;
            sz.Recorded = time;
            time.AddSeconds(1);
            entries.Add(sz);
            prev = sz;

            //now the program generates <size> lines of random data
            for (int i = 0; i < size; i++)
            {
                Snapshot next = new Snapshot();
                next.XAcceleration = newAcc(prev.XAcceleration);
                next.YAcceleration = newAcc(prev.YAcceleration);
                next.ZAcceleration = newAcc(prev.ZAcceleration);
                next.Recorded = time;
                time.AddSeconds(1);

                if (normal)
                {
                    next.Speed = newSpeed(prev.Speed);
                    entries.Add(next);
                    prev = next;
                }
                else
                    switch (r.Next(10))
                    {
                        case 0:
                            next.Speed = 0;
                            entries.Add(next);
                            break;
                        case 1:
                            if (allowNegSpeed)
                            {
                                if (r.Next(2) == 0)
                                    next.Speed = -(r.Next(100) + r.NextDouble());
                                else
                                    next.Speed = r.Next(100) + r.NextDouble();
                            }
                            else
                                next.Speed = r.Next(100) + r.NextDouble();
                            entries.Add(next);
                            break;
                        default:
                            next.Speed = newSpeed(prev.Speed);
                            entries.Add(next);
                            prev = next;
                            break;
                    }
            }

            logs = new string[entries.Count];
            int index = 0;
            foreach (Snapshot s in entries)
            {
                logs[index] = s.ToString();
                index++;
            }
        }

        public string[] GenerateLogFiles()
        {
            List<string> logFiles = new List<string>();

            StringBuilder sb = new StringBuilder();
            for (int i = 1; i <= logs.Length; i++)
            {
                sb.Append(logs[i - 1]);
                sb.Append(",");
                if ((i % 10) == 0)
                {
                    logFiles.Add(sb.ToString());
                    sb.Clear();
                }
            }

            logFiles.Add(sb.ToString());

            return logFiles.ToArray();
        }
    }

    class Snapshot
    {
        public int Entry { get; set; }
        public double Fuel { get; set; }
        public DateTime GPSFix { get; set; }
        public double Heading { get; set; }
        public double Latitude { get; set; }
        public double Longitude { get; set; }
        public double XAcceleration { get; set; }
        public double YAcceleration { get; set; }
        public double ZAcceleration { get; set; }
        public DateTime Recorded { get; set; }
        public string RPM { get; set; }
        public double Speed { get; set; }
        public string Throttle { get; set; }
        public string VIN { get; set; }
        public string Version { get; set; }
        public Snapshot previous { get; set; }

        public Snapshot()
        {
            this.RPM = "0";
            this.Throttle = "0";
            this.Heading = 0;
            this.Latitude = 0;
            this.Longitude = 0;
            this.VIN = "2FMDK4KC9DBA59459";
            this.Version = "1.1";
        }

        public string toString()
        {
            string summary = this.Recorded.ToString("dd-MM-yyy hh:mm:ss") + "," + this.VIN + "," + this.Speed + "," + this.XAcceleration + "," + this.YAcceleration + ","
                + this.ZAcceleration + "," + this.Latitude + "," + this.Longitude + "," + this.Heading + "," + this.RPM + "," + this.Throttle;
            return summary;
        }

        public override string ToString()
        {
            string[] keys = { "Entry", "Fuel", "GPSFix", "Heading", "Latitude", "Longitude", "XAcceleration", "YAcceleration", "ZAcceleration", "Recorded", "RPM", "Speed", "Throttle", "VIN", "Version" };

            StringBuilder sb = new StringBuilder("{");
            foreach (string key in keys)
            {
                string value = "";

                string sep = (key == keys[0]) ? "" : ",";

                sb.Append(sep);
                sb.Append("\"" + key + "\"");
                sb.Append(": ");

                try
                {
                    System.Reflection.MethodInfo method = this.GetType().GetMethod("get_" + key);
                    object obj = method.Invoke(this, null);
                    if (obj.GetType() == typeof(double))
                        value = ((double)obj).ToString("F3");
                    else if (obj.GetType() == typeof(int))
                        value = ((int)obj).ToString();
                    else if (obj.GetType() == typeof(DateTime))
                        value = "\"" + ((DateTime)obj).ToString("MM-dd-yyyy H:mm:ss") + "\"";
                    else
                        value = "\"" + obj.ToString() + "\"";

                    sb.Append(value);
                }
                catch (System.NullReferenceException ex)
                {
                    sb.Append("null");
                    Debug.Print(key + " caused an exception. " + ex.Message);
                }
            }
            sb.Append("}");

            return sb.ToString();
        }
    }
}
